﻿using System;
using System.Collections.Generic;
using System.Threading.Tasks.Dataflow;
using NLog;
using SunnyTrader.Common;

namespace QuantBox.XApi
{
    internal class SgdDealProcessor
    {
        private readonly SgdTradeClient _client;
        private readonly ActionBlock<TradingEvent> _action;
        private readonly OrderMap _orders = new OrderMap();
        private readonly HashSet<string> _tradeSet = new HashSet<string>();
        private readonly HashSet<string> _cancelPending = new HashSet<string>();
        private readonly List<TradeInforField> _tradePending = new List<TradeInforField>();
        private readonly Logger _logger;

        public SgdDealProcessor(SgdTradeClient client)
        {
            _client = client;
            _logger = client.logger;
            _action = new ActionBlock<TradingEvent>(ProcessEvent);
        }

        private void ProcessEvent(TradingEvent e)
        {
            switch (e.Type)
            {
                case TradingEventType.ReqInsert:
                    ProcessNewOrder(e.NewOrder!);
                    break;
                case TradingEventType.RspInsertOrder:
                    ProcessOrderReject(e.InputOrder!.Value, e.RspInfo!.Value);
                    break;
                case TradingEventType.ReqCancel:
                    ProcessCancelOrder(e.CancelOrderId!);
                    break;
                case TradingEventType.RspCancelOrder:
                    ProcessCancelReject(e.Order!.Value, e.RspInfo!.Value);
                    break;
                case TradingEventType.ReturnTrade:
                    ProcessRtnTrade(e.Trade!.Value);
                    break;
                case TradingEventType.ReturnOrder:
                    ProcessRtnOrder(e.Order!.Value);
                    break;
                default:
                    throw new ArgumentOutOfRangeException();
            }
        }

        private void ProcessCancelReject(OrderInforField field, ApiRspInfo rspInfo)
        {
            if (rspInfo.ErrorID != 0)
            {
                return;
            }
            if (_orders.TryGetBySysId(field.OrderSysID, out var order))
            {
                _logger.Debug($"撤单失败,id:{order.ID},oid:{order.OrderID},{rspInfo.ErrorMsg}");
            }
        }

        private void ReportOrder(OrderField order, ExecType execType, OrderStatus newStatus, ApiRspInfo rspInfo)
        {
            ReportOrder(order, execType, newStatus, rspInfo.ErrorID, rspInfo.ErrorID, rspInfo.ErrorMsg);
        }

        private void ReportOrder(
            OrderField order,
            ExecType execType,
            OrderStatus newStatus,
            int errorId = 0, int rawErrorId = 0, string text = "")
        {
            order.Status = newStatus;
            var report = order.Clone();
            report.ExecType = execType;
            if (!string.IsNullOrEmpty(text))
            {
                report.XErrorID = errorId;
                report.RawErrorID = rawErrorId;
                report.SetText(text);
            }
            _client.spi.ProcessRtnOrder(report);
        }

        private void ProcessTradePending()
        {
            if (_tradePending.Count == 0)
            {
                return;
            }
            var list = new List<TradeInforField>(_tradePending);
            _tradePending.Clear();
            foreach (var trade in list)
            {
                ProcessRtnTrade(trade, false);
            }
        }

        private void ProcessRtnTrade(TradeInforField data, bool sendLog = true)
        {
            if (sendLog)
            {
                _client.spi.ProcessLog(new LogField(LogLevel.Trace, "RtnTrade"));
            }
            if (_orders.TryGetBySysId(data.OrderSysID, out var order))
            {
                if (_tradeSet.Contains(data.TradeID))
                {
                    _logger.Warn($"重复的成交,oid:{data.OrderSysID},tid:{data.TradeID}");
                    return;
                }
                _tradeSet.Add(data.TradeID);

                _logger.Debug($"新成交,id:{order.ID},oid:{data.OrderSysID},tid:{data.TradeID},text:{order.Text()}");
                var trade = SgdConvert.GetTradeField(data);
                trade.ID = order.ID;
                _client.spi.ProcessRtnTrade(trade);
            }
            else
            {
                _logger.Debug($"成交暂存,oid:{data.OrderSysID},tid:{data.TradeID}");
                _tradePending.Add(data);
            }
        }
        private void ProcessRtnOrder(OrderInforField data)
        {
            var status = SgdConvert.GetOrderStatus(data.OrderStatus);
            if (!_orders.TryGetByOrderRef(data.OrderRef, out var order))
            {
                _logger.Debug($"外部订单,oid:{data.OrderSysID},{status}");
                return;
            }

            _logger.Debug($"订单回报,id:{order!.ID},ref:{data.OrderRef},oid:{data.OrderSysID},{status},text:{order.Text()}");
            if (data.OrderSysID == order.OrderID && status == order.Status)
            {
                _logger.Debug($"订单状态重复,id:{order!.ID},ref:{data.OrderRef},oid:{data.OrderSysID},{status}");
                return;
            }

            if (status == OrderStatus.Cancelled && order.ExecType != ExecType.PendingCancel)
            {
                _logger.Warn($"外部撤销,id:{order!.ID},ref:{data.OrderRef},oid:{data.OrderSysID},text:{order.Text()}");
            }

            var id = order.ID;
            if (!string.IsNullOrEmpty(data.OrderSysID) && string.IsNullOrEmpty(order.OrderID))
            {
                order.OrderID = data.OrderSysID;
                order.ExchangeID = SgdConvert.GetExchangeName(data.ExchangeID);
                _orders.SetMap(order);
                ProcessTradePending();
            }

            ReportOrder(
                order,
                SgdConvert.GetExecType(data.OrderStatus),
                SgdConvert.GetOrderStatus(data.OrderStatus),
                0,
                0,
                data.StatusMsg);

            if (_cancelPending.Contains(id))
            {
                _cancelPending.Remove(id);
                ProcessCancelOrder(id);
            }
        }

        private void ProcessCancelOrder(string id)
        {
            if (_cancelPending.Contains(id))
            {
                _logger.Warn($"重复撤单,id:{id}");
                return;
            }

            if (!_orders.TryGetById(id, out var order))
            {
                _logger.Warn($"撤销已完成的订单,id:{id}");
                return;
            }

            if (string.IsNullOrEmpty(order.OrderID))
            {
                _logger.Debug($"撤单缓存,id:{id}");
                _cancelPending.Add(id);
                return;
            }

            _logger.Debug($"订单撤销,id:{id},oid:{order.OrderID},text:{order.Text()}");
            order.ExecType = ExecType.PendingCancel;
            _client.api!.OrderAction(SgdConvert.GetExchangeId(order.ExchangeID), order.OrderID);
        }

        private void ProcessNewOrder(OrderField newOrder)
        {
            _orders.AddOrder(newOrder);
            if (newOrder.Status == OrderStatus.NotSent)
            {
                var data = SgdConvert.GetInputOrder(newOrder);
                _logger.Debug($"订单发送,id:{newOrder.ID},ref:{data.OrderRef},text:{newOrder.Text()}");
                data.InvestorID = _client.sgdLoginField.UserID;
                data.BrokerID = _client.sgdLoginField.BrokerID;
                newOrder.ExecType = ExecType.PendingNew;
                var ret = _client.InternalSend(ref data);
                if (ret == 0)
                {
                    return;
                }

                newOrder.ExecType = ExecType.Rejected;
                ProcessOrderReject(data, new ApiRspInfo
                {
                    ErrorID = -1,
                    ErrorMsg = "连接中断发送失败"
                });
            }
        }

        private void ProcessOrderReject(InputOrderInforField inputOrder, ApiRspInfo rspInfo)
        {
            var orderRef = inputOrder.OrderRef;
            if (_orders.TryGetByOrderRef(orderRef, out var order))
            {
                _logger.Debug($"无效订单,id:{order!.ID},ref:{orderRef},{rspInfo.ErrorMsg},text:{order.Text()}");
                ReportOrder(order, ExecType.Rejected, OrderStatus.Rejected, rspInfo);
            }
            else
            {
                _logger.Debug($"外部无效订单,ref:{orderRef},{rspInfo.ErrorMsg}");
            }
        }

        public void Close()
        {
            _action.Complete();
            _action.Completion.Wait();
        }

        internal void Post(TradingEvent e)
        {
            _action.Post(e);
        }
    }
}